package packet

import (
	"encoding/binary"
	"fmt"
	"net"
	"time"
)

type PacketType uint8

const (
	// UDP packet types
	PacketTypeHead PacketType = iota
	PacketTypePartial
	PacketTypeTail
	PacketTypeHeadWithTail
	PacketTypeAck
	PacketTypeHeartbeat
	PacketTypeReset
	PacketTypePing
	PacketTypeClose
)

type UdpPacket interface {
	GetRemoteAddr() net.Addr
	GetPayload() []byte
	GetType() PacketType
	GetSeq() uint16
	GetRecvTime() time.Time
	NeedAck() bool
	IsHead() bool
	IsTail() bool
}

type Packet struct {
	PacketHeader
	Payload []byte
}

type PacketHeader struct {
	VerID uint8
	Seq   uint16
	// Ack whether needs ack.
	Ack        bool
	isTail     bool
	isHead     bool
	Type       PacketType
	RemoteAddr net.Addr
	RecvTime   time.Time
}

type HeadPacket struct {
	Packet
	CRC uint32
	// DataLength indicates the total data length of a business/transaction data.
	DataLength uint32
}

type RawPacket struct {
	// DataLength the data length of a single packet, including header and payload parts.
	DataLength int
	PacketData []byte
	RemoteAddr net.Addr
}

type Err int

const (
	ErrInvalidPacketDataLength Err = iota
	ErrInvalidProtoID
)

var errString = map[Err]string{
	ErrInvalidPacketDataLength: "Invalid packet data length. Data length should >= 2",
	ErrInvalidProtoID:          "Invalid protocol ID",
}

func (e Err) Error() string {
	if s, ok := errString[e]; !ok {
		panic("Invalid err type.")
	} else {
		return s
	}
}

func (p *PacketHeader) GetRecvTime() time.Time {
	return p.RecvTime
}

func (p *PacketHeader) GetPayload() []byte {
	return nil
}

func (p *PacketHeader) GetRemoteAddr() net.Addr {
	return p.RemoteAddr
}

func (p *PacketHeader) GetType() PacketType {
	return p.Type
}

func (p *PacketHeader) GetSeq() uint16 {
	return p.Seq
}

func (p *PacketHeader) NeedAck() bool {
	return p.Ack
}

func (p *PacketHeader) IsHead() bool {
	return p.isHead
}
func (p *PacketHeader) IsTail() bool {
	return p.isTail
}

func (p *Packet) GetPayload() []byte {
	return p.Payload
}

func NewPacket(data []byte, remoteAddr net.Addr) (UdpPacket, error) {
	if len(data) < 2 {
		return nil, ErrInvalidPacketDataLength
	}
	header := binary.BigEndian.Uint16(data[0:2])
	verID := uint8(header >> 14)
	if verID != CURRENT_PROTO_ID {
		return nil, ErrInvalidProtoID
	}
	var needAck bool
	if (header<<2)>>15 != 0 {
		needAck = true
	}

	// PacketType
	packetType := PacketType((header << 3) >> 13)

	seq := (header << 6) >> 6

	var payloadOffset int
	switch packetType {
	case PacketTypeHeartbeat, PacketTypeAck, PacketTypeReset, PacketTypePing, PacketTypeClose:
		packet := new(PacketHeader)
		packet.Type = packetType
		packet.Seq = seq
		packet.VerID = verID
		packet.RemoteAddr = remoteAddr
		packet.Ack = needAck
		packet.RecvTime = time.Now()
		return packet, nil
	case PacketTypePartial, PacketTypeTail:
		packet := new(Packet)
		packet.Type = packetType
		packet.Seq = seq
		packet.Ack = needAck
		packet.VerID = verID
		if packetType == PacketTypeTail {
			packet.isTail = true
		}

		// offset fixed header(2)
		payloadOffset = 2
		packet.Payload = make([]byte, len(data[payloadOffset:]))
		copy(packet.Payload, data[payloadOffset:])
		packet.RemoteAddr = remoteAddr
		packet.RecvTime = time.Now()
		return packet, nil
	case PacketTypeHead, PacketTypeHeadWithTail:
		packet := new(HeadPacket)
		packet.Type = packetType
		packet.Seq = seq
		packet.Ack = needAck
		packet.VerID = verID
		packet.isHead = true
		if len(data) < 10 {
			return nil, fmt.Errorf("Head packet requires at least 10 bytes, but got only %v.", packetType, len(data))
		}
		packet.DataLength = binary.BigEndian.Uint32(data[2:6])
		packet.CRC = binary.BigEndian.Uint32(data[6:10])
		if packetType == PacketTypeHeadWithTail {
			packet.isTail = true
		}
		// offset fixed header(2) + variable header(8)
		payloadOffset = 10
		packet.Payload = make([]byte, len(data[payloadOffset:]))
		copy(packet.Payload, data[payloadOffset:])
		packet.RemoteAddr = remoteAddr
		packet.RecvTime = time.Now()
		return packet, nil
	default:
		return nil, fmt.Errorf("unexpected packet type %v", packetType)
	}
}

func NewAck(sn uint16) []byte {
	headerInt := uint16(CURRENT_PROTO_ID) << 14
	headerInt += uint16(PacketTypeAck) << 10
	headerInt += sn
	data := make([]byte, 2)
	binary.BigEndian.PutUint16(data[:], headerInt)
	return data
}

func NewHeartbeat() []byte {
	headerInt := uint16(CURRENT_PROTO_ID) << 14
	headerInt += uint16(PacketTypeHeartbeat) << 10
	data := make([]byte, 2)
	binary.BigEndian.PutUint16(data[:], headerInt)
	return data
}

func NewReset() []byte {
	headerInt := uint16(CURRENT_PROTO_ID) << 14
	headerInt += uint16(PacketTypeReset) << 10
	data := make([]byte, 2)
	binary.BigEndian.PutUint16(data[:], headerInt)
	return data
}

func NewPing(sn uint16) []byte {
	headerInt := uint16(CURRENT_PROTO_ID) << 14
	headerInt += uint16(1) << 13
	headerInt += uint16(PacketTypePing) << 10
	headerInt += sn
	data := make([]byte, 2)
	binary.BigEndian.PutUint16(data[:], headerInt)
	return data
}

func NewClose()[]byte{
	headerInt := uint16(CURRENT_PROTO_ID) << 14
	headerInt += uint16(PacketTypeClose) << 10
	data := make([]byte, 2)
	binary.BigEndian.PutUint16(data[:], headerInt)
	return data
}
